/**
 * @licstart The following is the entire license notice for the
 * JavaScript code in this page
 *
 * Copyright 2022 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @licend The above is the entire license notice for the
 * JavaScript code in this page
 */
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.PredictorStream = void 0;

var _decode_stream = require("./decode_stream.js");

var _primitives = require("./primitives.js");

var _util = require("../shared/util.js");

class PredictorStream extends _decode_stream.DecodeStream {
  constructor(str, maybeLength, params) {
    super(maybeLength);

    if (!(params instanceof _primitives.Dict)) {
      return str;
    }

    const predictor = this.predictor = params.get("Predictor") || 1;

    if (predictor <= 1) {
      return str;
    }

    if (predictor !== 2 && (predictor < 10 || predictor > 15)) {
      throw new _util.FormatError(`Unsupported predictor: ${predictor}`);
    }

    if (predictor === 2) {
      this.readBlock = this.readBlockTiff;
    } else {
      this.readBlock = this.readBlockPng;
    }

    this.str = str;
    this.dict = str.dict;
    const colors = this.colors = params.get("Colors") || 1;
    const bits = this.bits = params.get("BPC", "BitsPerComponent") || 8;
    const columns = this.columns = params.get("Columns") || 1;
    this.pixBytes = colors * bits + 7 >> 3;
    this.rowBytes = columns * colors * bits + 7 >> 3;
    return this;
  }

  readBlockTiff() {
    const rowBytes = this.rowBytes;
    const bufferLength = this.bufferLength;
    const buffer = this.ensureBuffer(bufferLength + rowBytes);
    const bits = this.bits;
    const colors = this.colors;
    const rawBytes = this.str.getBytes(rowBytes);
    this.eof = !rawBytes.length;

    if (this.eof) {
      return;
    }

    let inbuf = 0,
        outbuf = 0;
    let inbits = 0,
        outbits = 0;
    let pos = bufferLength;
    let i;

    if (bits === 1 && colors === 1) {
      for (i = 0; i < rowBytes; ++i) {
        let c = rawBytes[i] ^ inbuf;
        c ^= c >> 1;
        c ^= c >> 2;
        c ^= c >> 4;
        inbuf = (c & 1) << 7;
        buffer[pos++] = c;
      }
    } else if (bits === 8) {
      for (i = 0; i < colors; ++i) {
        buffer[pos++] = rawBytes[i];
      }

      for (; i < rowBytes; ++i) {
        buffer[pos] = buffer[pos - colors] + rawBytes[i];
        pos++;
      }
    } else if (bits === 16) {
      const bytesPerPixel = colors * 2;

      for (i = 0; i < bytesPerPixel; ++i) {
        buffer[pos++] = rawBytes[i];
      }

      for (; i < rowBytes; i += 2) {
        const sum = ((rawBytes[i] & 0xff) << 8) + (rawBytes[i + 1] & 0xff) + ((buffer[pos - bytesPerPixel] & 0xff) << 8) + (buffer[pos - bytesPerPixel + 1] & 0xff);
        buffer[pos++] = sum >> 8 & 0xff;
        buffer[pos++] = sum & 0xff;
      }
    } else {
      const compArray = new Uint8Array(colors + 1);
      const bitMask = (1 << bits) - 1;
      let j = 0,
          k = bufferLength;
      const columns = this.columns;

      for (i = 0; i < columns; ++i) {
        for (let kk = 0; kk < colors; ++kk) {
          if (inbits < bits) {
            inbuf = inbuf << 8 | rawBytes[j++] & 0xff;
            inbits += 8;
          }

          compArray[kk] = compArray[kk] + (inbuf >> inbits - bits) & bitMask;
          inbits -= bits;
          outbuf = outbuf << bits | compArray[kk];
          outbits += bits;

          if (outbits >= 8) {
            buffer[k++] = outbuf >> outbits - 8 & 0xff;
            outbits -= 8;
          }
        }
      }

      if (outbits > 0) {
        buffer[k++] = (outbuf << 8 - outbits) + (inbuf & (1 << 8 - outbits) - 1);
      }
    }

    this.bufferLength += rowBytes;
  }

  readBlockPng() {
    const rowBytes = this.rowBytes;
    const pixBytes = this.pixBytes;
    const predictor = this.str.getByte();
    const rawBytes = this.str.getBytes(rowBytes);
    this.eof = !rawBytes.length;

    if (this.eof) {
      return;
    }

    const bufferLength = this.bufferLength;
    const buffer = this.ensureBuffer(bufferLength + rowBytes);
    let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);

    if (prevRow.length === 0) {
      prevRow = new Uint8Array(rowBytes);
    }

    let i,
        j = bufferLength,
        up,
        c;

    switch (predictor) {
      case 0:
        for (i = 0; i < rowBytes; ++i) {
          buffer[j++] = rawBytes[i];
        }

        break;

      case 1:
        for (i = 0; i < pixBytes; ++i) {
          buffer[j++] = rawBytes[i];
        }

        for (; i < rowBytes; ++i) {
          buffer[j] = buffer[j - pixBytes] + rawBytes[i] & 0xff;
          j++;
        }

        break;

      case 2:
        for (i = 0; i < rowBytes; ++i) {
          buffer[j++] = prevRow[i] + rawBytes[i] & 0xff;
        }

        break;

      case 3:
        for (i = 0; i < pixBytes; ++i) {
          buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
        }

        for (; i < rowBytes; ++i) {
          buffer[j] = (prevRow[i] + buffer[j - pixBytes] >> 1) + rawBytes[i] & 0xff;
          j++;
        }

        break;

      case 4:
        for (i = 0; i < pixBytes; ++i) {
          up = prevRow[i];
          c = rawBytes[i];
          buffer[j++] = up + c;
        }

        for (; i < rowBytes; ++i) {
          up = prevRow[i];
          const upLeft = prevRow[i - pixBytes];
          const left = buffer[j - pixBytes];
          const p = left + up - upLeft;
          let pa = p - left;

          if (pa < 0) {
            pa = -pa;
          }

          let pb = p - up;

          if (pb < 0) {
            pb = -pb;
          }

          let pc = p - upLeft;

          if (pc < 0) {
            pc = -pc;
          }

          c = rawBytes[i];

          if (pa <= pb && pa <= pc) {
            buffer[j++] = left + c;
          } else if (pb <= pc) {
            buffer[j++] = up + c;
          } else {
            buffer[j++] = upLeft + c;
          }
        }

        break;

      default:
        throw new _util.FormatError(`Unsupported predictor: ${predictor}`);
    }

    this.bufferLength += rowBytes;
  }

}

exports.PredictorStream = PredictorStream;